import { onBeforeMount, onUnmounted, getCurrentInstance } from "vue";
import { isNil, isEmpty, mergeDeepRight, clone, mergeAll } from "ramda";
import type { RouteRecordRaw, Router, RouteComponent, RouteLocationNamedRaw } from "vue-router";
import * as VueRouter from 'vue-router';

/* types   ---------- ---------- ---------- ---------- ---------- ----------*/
declare type Lazy<T> = () => Promise<T>;
/**
 * 路由对象类型约束 继承自 RouteRecordRaw
 */
export declare type VmoxRouteRecordRaw = Omit<RouteRecordRaw,'meta'|'children'|'component'> & {
  meta?:{
    pageName: string // 页面标题
    tokenRequire?: boolean // 登录约束，如果为false 或者
    powerRequire?: string[] // 前端权限，当路由被后端管理时，不做约束
    avoidTabMenu?: boolean // 不添加至标签菜单 true 不允许添加到菜单
    unConfigurable?: boolean // 该页面是否允许进入动态配置 true 不允许
    keepAlive?: boolean // 是否存入缓存，true存入 false 免于存入
  },
  children?:VmoxRouteRecordRaw[]
  component?:RouteComponent | Lazy<RouteComponent>;
}
/**
 * 路由菜单配置数据
 * 该类型主要用于后端存储，或前端配置来实现菜单呈现
 * 该类型数据包括了菜单所需的 label, icon , 模版所需的 template ，路由所需的其 name,path,query,params 等
 */
export declare type VmoxRouteLocationNamedRaw = {
  label?:string, // 菜单标题
  icon?:string, // 菜单图标
  template?:{ // 菜单模版信息
    pageKey:string, // 模版主键名称，对应 PGS 内的对象属性名称；
    parent?:string, // 父路由情况
    route?:Omit<VmoxRouteRecordRaw,'name'> // 生成的路由配置
  },
  children?:VmoxRouteLocationNamedRaw[] // 是否有子对象
} & RouteLocationNamedRaw
/* methods ---------- ---------- ---------- ---------- ---------- ----------*/
/**
 * 动态路由添加判断方法
 * @param routeLocationNamedRaw 动态路由添加所需的配置
 * @returns {boolean} 
 */
export function allowCreateRouteRecordRawByTemplate(routeLocationNamedRaw:VmoxRouteLocationNamedRaw, pageTemplates:{[key: string]: RouteRecordRaw} ):boolean{
  if ([ routeLocationNamedRaw?.name, routeLocationNamedRaw?.template, pageTemplates[routeLocationNamedRaw?.template?.pageKey as string] ].filter((item:any) => isNil(item) || isEmpty(item)).length == 0){
    return true
  }else{
    console.error(`【ERROR】创建动态路由失败: 
    \n [routeLocationNamedRaw?.name]:${routeLocationNamedRaw?.name as string}
    \n [routeLocationNamedRaw?.template]:${routeLocationNamedRaw?.template?.pageKey as string}
    \n PGS[routeLocationNamedRaw?.template]:${pageTemplates[routeLocationNamedRaw?.template?.pageKey as string]}
    \n 请补全以上参数`);
    console.log(pageTemplates)
    return false
  }
};

/**
 * 依据模版创建动态路由创建
 * @param routeLocationNamedRaw 动态路由添加所需的配置
 * @param autoAddToRouter 是否同步完成添加操作
 * @param routerInstance 路由对象实例
 * @param pageTemplates 路由模版持有者
 * @returns {VmoxRouteLocationNamedRaw|void} 
 */
export async function createRouteRecordRawByTemplate( routeLocationNamedRaw:VmoxRouteLocationNamedRaw , autoAddToRouter:boolean = false, pageTemplates:{[key: string]: RouteRecordRaw}, routerInstance?:Router ):Promise<RouteComponent|void>{
  const prototype = mergeDeepRight( clone(pageTemplates[routeLocationNamedRaw?.template?.pageKey as string]), routeLocationNamedRaw?.template?.route || {}); // 合成模版数据
  if(!isNil(prototype.component)){
    prototype.component = prototype.component.constructor == Function ? _routePageComponentLoader.bind({name:routeLocationNamedRaw.name},prototype.component) : prototype.component; // 指定上下文做好异步加载准备
    prototype.name = routeLocationNamedRaw.name;
    if( autoAddToRouter && routerInstance ){
      // 判断添加的路由是否有父级路由
      if(routeLocationNamedRaw.template?.parent && routerInstance.hasRoute(routeLocationNamedRaw.template?.parent)){
        prototype.path = prototype.path.replace(/^\/+/g,''); // 如果存在父级路由，则需要去除地址中意 / 开头的情况
        routerInstance.addRoute(routeLocationNamedRaw.template?.parent,prototype); // 在指定的父路由下，添加路由
      }else{
        prototype.path = (!/^\/.*/.test(prototype.path)) ?'/' + prototype.path : prototype.path; // 如果不存在父级别路由，则需要检查是否携带/开头，如果没有，则需要补充
        routerInstance.addRoute(prototype); // 直接添加路由
      };
    }
    return prototype as RouteComponent;
  }
};

/**
 * 异步页面加载器,加载器具备路由更名功能
 * @param this // 上下文，组件加载后，会修改组件内的自定义命名，符合加载规则
 * @param component // ()=>import("xxxx.vue") 异步加载的组件加载器
 * @returns function // 返回最终的加载器
 * 在懒加载的路由添加模式中，如果简单采用复制的方式，将会导致路由被重复声明内存空间，这将导致两个问题
 * 1.开发时，不得不采用刷新页面的方式，才能看到热更；
 * 2.生产时，内存空间被不断的过度消耗，所以目前采用更为轻巧的模式，只是浅拷贝，保持热更的同时，也能告诉路由缓存对应的内容;
 */
async function _routePageComponentLoader(this:any,component:any){
  const context = this;
  const module = await component();
  // 保守方法;
  // const compdata = clone(module.default ? module.default : module); // 安全写法，直接复制，避免内存污染的问题，但是目前该方法不能维持 vite 的热更响应，需要开发时手动刷新页面，才可以更新内容
  // const uniqID = Date.now().toString(32).toUpperCase();
  // compdata.name = context.name;
  // compdata.__hmrId = uniqID;
  // 新方法
  const compdata = mergeAll([module.default ? module.default : module, { name:context.name }])
  return Promise.resolve(compdata);
}


/* main ---------- ---------- ---------- ---------- ---------- ----------*/
/**
 * 路由缓存数据类型定义
 * @param dynamicRoutes 动态添加到路由里的配置数据；
 * @param keepAlivePage 需要缓存的内存路由名称
 * @param dynamicCatchType 缓存模式 为ture 时，会缓存所有的路由表，false 只缓存当前路由，此设置可配合 token机制，做到浏览器开启新标签是否能打开用户获得授权的任意页面，或直接地址跳转；
 */
export namespace ProxyRouter {
  export type DynamicRouter = Router & {
    reloadRoutes:()=>void, 
    cleaRoutes:()=>void,
    generateRousteByTreeData:(routes:VmoxRouteLocationNamedRaw[])=>Promise<boolean|void>
  }
  export type ProxyRouterOptions = {
    catchStore : Record<string,any>, // 缓存状态数据 pinia
    pageTemplates : {[key: string]: RouteRecordRaw } // 模板集合数据
    mutipleCatch : boolean // 缓存类型 // 为ture 时，会缓存所有的路由表，false 只缓存当前路由，此设置可配合 token机制，做到浏览器开启新标签是否能打开用户获得授权的任意页面，或直接地址跳转，
  }
};

// 包内代理路由实例
let _proxyRouter:ProxyRouter.DynamicRouter|null = null;
let _catchStore:Record<string,any>|null = null
/**
 * 获取代理路由
 * @returns {ProxyRouter.DynamicRouter|void} 返回被代理的路由对象
 */
export const useRouter =():ProxyRouter.DynamicRouter|void=>{
  if(_proxyRouter){
    return _proxyRouter;
  }else{
    console.warn('代理路由还未初始化，请在初始化代理路由后，在调用此方法');
  }
};

/**
 * 获取路由实例
 * @returns {VueRouter.RouteLocationNormalizedLoaded} 返回的当前路由实例
 */
export const useRoute =():VueRouter.RouteLocationNormalizedLoaded=>{
  return VueRouter.useRoute()
};

/**
 * 创建vue 代理路由实例;
 * @param targetRouter // vue 原始路由实例
 * @param proxyOption // 代理所需配置
 * @returns {ProxyRouter.DynamicRouter} //被代理后的路由
 */
export function setProxyRouter( targetRouter:Router, proxyOption:ProxyRouter.ProxyRouterOptions):ProxyRouter.DynamicRouter {
  _catchStore = proxyOption.catchStore;
  _catchStore.setMutipleCatch( isNil(proxyOption.mutipleCatch) || proxyOption.mutipleCatch.constructor != Boolean ? true : proxyOption.mutipleCatch );
  const pageTemplates = proxyOption.pageTemplates;
  const vueBaseRouter = targetRouter;
  const methods = {
    /**
     * 添加动态路由
     * @param route 需要添加的动态路由配置
     * @returns {boolean} 返回成功或失败
     */
    addRoute:async (route:VmoxRouteLocationNamedRaw)=>{
      if( vueBaseRouter ){
        if( vueBaseRouter.hasRoute(route?.name as string) ){
          // _catchStore.insertDynamicRoutes(route); // 出现重复路由，会导致路由缓存keep Alive配置以最后一个为准的情况，已经存在的路由应该不需要再添加 因此暂时关闭此操作，测试后再决定是否要调整
          return true 
        };
        if( allowCreateRouteRecordRawByTemplate(route, pageTemplates) ){ 
          await createRouteRecordRawByTemplate(route, true, pageTemplates, vueBaseRouter as Router );
          _catchStore?.insertDynamicRoutes(route);
          return true;
        }else{
          return false;
        };
      }else{
        return false;
      };
    },

    /**
     * 需要被移除的路由数据
     * @param route 
     * @returns {void}
     */
    removeRoute:(route:VmoxRouteLocationNamedRaw|string)=>{
      const routeName = ( route as {name:string} )?.name||route as string;
      _catchStore?.removeDynamicRoutes(routeName);
      vueBaseRouter.removeRoute(( route as {name:string} )?.name||route as string);
    },

    /**
     * 路由跳转 push 方法代持
     * @param route
     * @returns {void} 
     */
    push:async(route:VmoxRouteLocationNamedRaw):Promise<void | VueRouter.NavigationFailure |undefined>=>{
      try{
        if(await methods.addRoute(route)){
         return await vueBaseRouter.push({name:route?.name, query:route.query, params:route.params});
        };
      }catch(err){
        console.log(err);
      };
    },
    
    /**
     * 路由跳转 replace 方式
     * @param route
     * @returns {void}  
     */
    replace:async(route:VmoxRouteLocationNamedRaw):Promise<void | VueRouter.NavigationFailure |undefined>=>{
      try{
        if(await methods.addRoute(route)){
          return await vueBaseRouter.replace({name:route?.name, query:route.query, params:route.params});
        };
      }catch(err){
        console.log(err);
      };
    },

    /**
     * 根据菜单树，生成路由
     * @param routes 需要添加的动态路由配置
     * @returns {boolean} 返回成功或失败
     */
    generateRousteByTreeData:async (routes:VmoxRouteLocationNamedRaw[])=>{
      routes.map(async (route)=>{
        await methods.addRoute(route);
        route.children && await methods.generateRousteByTreeData(route.children);
      });
    },
    
    /**
     * 重载所有缓存路由
     * @returns {void}
     */
    reloadRoutes:async ()=>{
      await _catchStore?.getDynamicRoutes.map(async (item:VmoxRouteLocationNamedRaw)=>{
        await methods.addRoute(item);
      });
    },
    /**
     * 清除所有缓存路由
     * @returns {void}
     */
    cleaRoutes:()=>{
      _catchStore?.getDynamicRoutes.forEach((item:VmoxRouteLocationNamedRaw)=>{
        methods.removeRoute(item);
      });
      _catchStore?.clearDynamicRouters();
    }
  };
  _proxyRouter = new Proxy(vueBaseRouter,{
    get(t,p){
      return (methods as {[key:string]:Function})[p as string] || (t as any)[p]
    },
    set(t,p,v){
      (t as any)[p]=v;
      return true;
    }
  }) as ProxyRouter.DynamicRouter
  return _proxyRouter;
};
/**
 * 阻止浏览器默认行为 刷新，返回，前进等 用于提示用户未保存填写数据场景
 * 使用方法如下:
 * 1.在vue root 节点上使用 usePreventBrowserBehavior() 方法 完成 window 对象绑定
 * 2.在页面上就可以通过 ../state useRouterStateStore() 返回的 store 来进行是否阻拦跳转属性的操作 getPreventNavigation setPreventNavigation
 * 3.在路由中，需要在跳转前 根据 store.getPreventNavigation 的状态作一次跳转提示 提示内容也可动态传递 通过 store.preventDialogContent 进行设置即可完成；
 */
export function usePreventBrowserBehavior(){
  const instance= getCurrentInstance();
  const preventStore = _catchStore;
  if(instance){
    onBeforeMount(()=>{
      window.addEventListener("beforeunload", preventNav);
    });
    onUnmounted(()=>{
      preventStore?.setPreventNavigation(false);
      window.removeEventListener("beforeunload", preventNav)
    });
  };
  function preventNav(event:BeforeUnloadEvent){
    if(!preventStore?.getPreventNavigation) return;
    event.preventDefault();
    event.returnValue = preventStore?.getPreventDialogContent?.message;
  };
};